/**
@author 迷途小羔羊
2022.09.27
*/
namespace GYLite
{
    /**贝塞尔端点类，用于控制贝塞尔曲线*/
	export class BezierPoint
	{		
		private _prePoint:BezierPoint;
		private _nextPoint:BezierPoint;
		private _frontControlPoint:egret.Point;
		private _backControlPoint:egret.Point;
		private _point:egret.Point;
		public constructor(x: number=0,y: number=0,s:Single=null)
		{
            let self = this;
			if(!s)
				throw(new Error("use getInstance to Create"));
			self._point = new egret.Point;
			self._frontControlPoint = new egret.Point;
			self._backControlPoint = new egret.Point;
			self._backControlPoint.x = self._frontControlPoint.x = self._point.x = x;
			self._backControlPoint.y = self._frontControlPoint.y = self._point.y = y;
		}
		public set x(val: number)
		{
			this._point.x = val;
		}
		public set y(val: number)
		{
			this._point.y = val;
		}
		public get x(): number
		{
			return this._point.x;
		}
		public get y(): number
		{
			return this._point.y;
		}		
		public set frontControlX(val: number)
		{
			this._frontControlPoint.x = val;
		}
		public set frontControlY(val: number)
		{
			this._frontControlPoint.y = val;
		}
		public get frontControlX(): number
		{
			return this._frontControlPoint.x;
		}
		public get frontControlY(): number
		{
			return this._frontControlPoint.y;
		}		
		public set backControlX(val: number)
		{
			this._backControlPoint.x = val;
		}
		public set backControlY(val: number)
		{
			this._backControlPoint.y = val;
		}
		public get backControlX(): number
		{
			return this._backControlPoint.x;
		}
		public get backControlY(): number
		{
			return this._backControlPoint.y;
		}
		public get prePoint():BezierPoint
		{
			return this._prePoint;
		}
		public set prePoint(val:BezierPoint)
		{
			this._prePoint = val;
		}
		public get nextPoint():BezierPoint
		{
			return this._nextPoint;
		}
		public set nextPoint(val:BezierPoint)
		{
			this._nextPoint = val;
		}
		public get frontControlPoint():Point
		{
			return this._frontControlPoint;
		}
		public get backControlPoint():Point
		{
			return this._backControlPoint;
		}
		public get point():Point
		{
			return this._point;
		}
		public clear():void
		{
            let s= this;
			if(s.prePoint)
			{
				s.prePoint.nextPoint = s.nextPoint?s.nextPoint:null;
			}
			if(s.nextPoint)
			{
				s.nextPoint.prePoint = s.prePoint?s.prePoint:null;
			}
			s._backControlPoint.x = s._frontControlPoint.x = s._point.x = 0;
			s._backControlPoint.y = s._frontControlPoint.y = s._point.y = 0;
			BezierPoint._pool.push(this);
		}
		/**计算3次贝塞尔的长度         
         * @param p0 起点
         * @param p1 起点的控制点
         * @param p2 终点的控制点
         * @param p3 终点
         * @param density 绘制点的密度
        */
		public static getThreeTimesLength(p0:Point, p1:Point, p2:Point, p3:Point, density: number = 0.05): number
		{
            let l:number=0;
            BezierPoint.drawThreeTimesCall(p0,p1,p2,p3,density,function(x:number,y:number,end:number):void{
                if(end == 0)
                {
                    BezierPoint.tempPoint1.x = x;
				    BezierPoint.tempPoint1.y = y;                    
                }                    
                else
                {
                    BezierPoint.tempPoint2.x = x;
				    BezierPoint.tempPoint2.y = y;
                    l += egret.Point.distance(BezierPoint.tempPoint1,BezierPoint.tempPoint2);
                    BezierPoint.tempPoint1.x = x;
				    BezierPoint.tempPoint1.y = y;
                }                
            });
            return l;			
		}
		/**绘制3次贝塞尔曲线的线条路径
         * @param g Graphics
         * @param p0 起点
         * @param p1 起点的控制点
         * @param p2 终点的控制点
         * @param p3 终点
         * @param density 绘制点的密度
		 * @param excludeEndPoint 不包括最后一个采样点，默认false，对于环绕的贝塞尔，我们可以剔除最后一个采样点，因为我们需要环接
        */
		public static drawThreeTimes(g:egret.Graphics, p0:Point, p1:Point, p2:Point, p3:Point, density: number = 0.05, excludeEndPoint:boolean=false):void
		{            
            BezierPoint.drawThreeTimesCall(p0,p1,p2,p3,density,function(x:number,y:number,end:number):void{
                if(end == 0)
                    g.moveTo(x,y);
                else if(!excludeEndPoint || end != 2)
                    g.lineTo(x,y);
                if(end == 2)
                    g.endFill();
            });			
		}
        /**绘制3次贝塞尔曲线的波点路径
         * @param g Graphics
         * @param p0 起点
         * @param p1 起点的控制点
         * @param p2 终点的控制点
         * @param p3 终点
         * @param density 绘制点的密度
		 * @param excludeEndPoint 不包括最后一个采样点，默认false，对于环绕的贝塞尔，我们可以剔除最后一个采样点，因为我们需要环接
        */
		public static drawThreeTimes2(g:egret.Graphics, p0:Point, p1:Point, p2:Point, p3:Point, density: number = 0.05, c:number = 0x74CDF7, r:number=3, excludeEndPoint:boolean=false):void
		{
            g.beginFill(c);
            BezierPoint.drawThreeTimesCall(p0,p1,p2,p3,density,function(x:number,y:number,end:number):void{
				if(!excludeEndPoint || end != 2)                    
                	g.drawCircle(x,y,r);
                if(end == 2)
                    g.endFill();
            });
		}
        /**绘制3次贝塞尔曲线的路径波点
         * @param g Graphics
         * @param p0 起点
         * @param p1 起点的控制点
         * @param p2 终点的控制点
         * @param p3 终点
         * @param density 绘制点的密度, 默认0.05
         * @param callBack (x:number,y:number,end:number); x,y坐标 end 0代表起点 1代表执行中 2代表结束
         * @param thisObj this指向		 
        */
		public static drawThreeTimesCall(p0:Point, p1:Point, p2:Point, p3:Point, density: number = 0.04, callBack:Function=null,thisObj:any=null):void
		{
			var toX: number,toY: number;
			var t: number,t1: number,t_2: number, t_3: number, t1_2: number, t1_3: number;
			var param1: number,param2: number;			
			var end:number=0;			
			var preX: number,preY: number,dx:number,dy:number,dis:number,preDis:number,k:number,per:number,preT:number;						
			// let standardDis:number;
			let minT:number,maxT:number;
			let ratio:number;
			let xV:number,yV:number;
			let continueFlag:boolean;
			let p0x:number,p0y:number,p1x:number,p1y:number,p2x:number,p2y:number,p3x:number,p3y:number;
			p0x = p0.x;
			p0y = p0.y;
			p1x = p1.x;
			p1y = p1.y;
			p2x = p2.x;
			p2y = p2.y;
			p3x = p3.x;
			p3y = p3.y;			
			if(callBack == null)return;
			let test:number=0;			
            ratio = minT = maxT = k = preDis = preX = preY = NaN;
			preT = 0;
			// standardDis = 14 * density/BezierPoint.defaultDensty;
			// standardDis *= standardDis;
			preX = toX = p0x;
			preY = toY = p0y;            
			// console.log("start:"+toX+","+toY);
			callBack.call(thisObj,p0x,p0y,end);
            ++end;			
			minT = k = t = density;
			while(1)
			{
				t1 = 1-t;
				t1_2 = t1 * t1;
				t1_3 = t1_2 * t1;
				t_2 = t * t;
				t_3 = t_2 * t;
				param1 = t1_2 * t * 3;//(1-t)^2*t*3----9t^2-12*t+3
				param2 = t_2 * t1 * 3;//t^2*(1-t)*3----6*t-9*t^2
				toX = p0x * t1_3 + p1x * param1 + p2x * param2 + p3x * t_3;
				toY = p0y * t1_3 + p1y * param1 + p2y * param2 + p3y * t_3;
                dx = toX - preX;
				dy = toY - preY;
				ratio = dy/dx;

				if(dy < 0)dy = -dy;                                    
				if(dx < 0)dx = -dx;
				dis = dy*dy+dx*dx;								
				
				let nextT:number;
				let mx:number,my:number;
				let nextX:number,nextY:number;
				let h:number;
				nextT = t + (t - preT);
				if(nextT >= 1)
					h = BezierPoint.chordMinH;
				else
				{
					t1 = 1-nextT;
					t1_2 = t1 * t1;
					t1_3 = t1_2 * t1;
					t_2 = nextT * nextT;
					t_3 = t_2 * nextT;
					param1 = t1_2 * nextT * 3;
					param2 = t_2 * t1 * 3;
					mx = p0x * t1_3 + p1x * param1 + p2x * param2 + p3x * t_3;
					my = p0y * t1_3 + p1y * param1 + p2y * param2 + p3y * t_3;
					nextX = (preX + mx)/2;
					nextY = (preY + my)/2;
					h = PositionUtil.calculateDistance2(toX,toY,nextX,nextY);
				}										
				++test;
				if(test > 100)
				{
					console.error(SysError.BEZIER_ERROR.throwError(["("+p0x+","+p0y+")-("+p1x+","+p1y+")-("+p2x+","+p2y+")-"+h+"-"+test]).msg);
					return;
				}				
				if(h > BezierPoint.chordMaxH)
				{//距离目标太远
					maxT = t;				
					t = (t + preT)/2;					
					continueFlag = true;
				}							
				else if(h < BezierPoint.chordMinH)
				{//过于接近
					minT = t;
					if(maxT == maxT)					
						t = (maxT + minT)/2;
					else
						t = nextT;
					continueFlag = true;
				}	
				
				if(dx <= 1 && dy <= 1)
				{
					t+=k;
					continueFlag = true;
				}

				if(t < 1 && continueFlag)	
				{
					continueFlag = false;
					continue;
				}

				xV = toX - preX;
				yV = toY - preY;
                preX = toX;
                preY = toY;
                preDis = dis;  
				minT = preT = t;
				maxT = NaN;
							
				if(t >= 1)
				{
					t = 1;
					toX = p3x;
					toY = p3y;
					end = 2;
				}
				else if(t <= 0)
				{
					t = 0;
					end = 2;
				}							
				// console.log(ratio,toX,toY);
				callBack.call(thisObj,toX,toY, end);
				if(end == 2)
				{
					// console.log("end:"+t);
					break;
				}				
				t += k;	
				if(t >= 1)
					t = 1;
				else if(t < 0)
					t = 0;
			}
		}
		/**贝塞尔采样的弦高最大值（像素）*/
		public static chordMaxH:number = 0.9;
		/**贝塞尔采样的弦高最小值（像素）*/
		public static chordMinH:number = 0.1;
		/**轻微倾斜最大值判断，用于矫正轻微倾斜时对衔接进行0.5辅助*/
		public static tinyTanMax:number= 1.73;
		/**轻微倾斜最小值判断，用于矫正轻微倾斜时对衔接进行0.5辅助*/
		public static tinyTanMin:number= 0.57;
		/**默认贝塞尔采样步进密度基准值*/
		public static defaultDensty:number=0.04;
		private static tempPoint1:egret.Point = new egret.Point;
		private static tempPoint2:egret.Point = new egret.Point;		
		private static _pool:BezierPoint[];
		private static _s:Single;
		public static getInstance(x: number=0,y: number=0):BezierPoint
		{
			if(!BezierPoint._s)
			{
				BezierPoint._s=new Single;
				BezierPoint._pool=[];
			}
			var p:BezierPoint;
			if(BezierPoint._pool.length==0)
				p = new BezierPoint(x,y,BezierPoint._s);
			else
				p =BezierPoint._pool.pop();
			p.x = x;
			p.y = y;
			return p;
		}
	}
    class Single{}
}
